package com.bestattempt.utility.bind.initializers.defaultImpl;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;

import static com.bestattempt.utility.reflection.ReflectionUtil.*;
import com.bestattempt.utility.bind.InitializerException;
import com.bestattempt.utility.bind.initializers.defaultImpl.ObjectInitializer;

/**
 * TODO: Document me
 *
 * @author Ganeshji Marwaha
 * @since Oct 14, 2006
 */
public class MapInitializer extends ObjectInitializer {
    public Object initialize(Object obj, Field field, String indexStr) {
        Map map = (Map) getFieldValue(obj, field);
        Class mapClass = field.getType();

        if(map == null) {
            map = mapClass.isInterface() ? newInstance(HashMap.class) : (Map) newInstance(mapClass);
        }

        return initMap(map, indexStr, obj, field);

    }

    private Object initMap(Map map, String indexStr, Object obj, Field field) {

        // If the map already contains the given index, then just return it, no need to initialize it.
        if(map.get(indexStr) != null) {
            return map.get(indexStr);
        }

        // If the Map's generic type is not a parameterized type, then there wont be enough information to initialize.
        Type type = field.getGenericType();
        if( !(type instanceof ParameterizedType) ) {
            throw new InitializerException("Not enough type information");
        }

        Type[] types = ((ParameterizedType)type).getActualTypeArguments();

        // We support only string key.
        Class keyType = (Class) types[0];
        if(!isStringOrNumericType(keyType)) {
            throw new InitializerException("We support only Numeric or String key in a map.");
        }

        Object o = newInstance(types[1]);
        map.put(indexStr, o);
        setFieldValue(obj, field, map);

        return o;
    }

    private boolean isStringOrNumericType(Class type) {
        return String.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type);
    }
}
